<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/swift-optional/">
<meta property="og:title" content="       Swift Probe - Optional | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       Swift Probe - Optional | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/swift-optional/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>Swift Probe - Optional</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2017-07-14"> 2017-07-14
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <p>最近在研究 Swift 中好玩的东西，打算将一些学习笔记，整理成一个系列便于自己温习且与大家交流。这次来玩弄一下 Optional。</p>
<h2 id="optional-">Optional 引入由来</h2>
<p>Optional 特性是 Swift 中的一大特色，用来解决变量是否存有 <code>nil</code> 值的情况。这样既可减少在数据传递过程中，由于 <code>nil</code> 带来的不确定性，防止未处理 <code>nil</code> 而带来的程序崩溃。</p>
<p>Optional 在高级语言中其实并不是 Swift 的首创，而是效仿其他语言学习来的特性。2015 年的时候，为了迎合 Swift 的 Optional 特性，在 Objective-C 中也引入了 Nullability 特性。Swift 作为一个强类型语言，需要在编译期进行安全检查，所以引入了类型推断的特性。为了保证推断的安全，于是又引入了 Optional 特性。</p>
<p>如果没有 Optional 到底有如何的危险呢？我们用 C++ 的一个例子来看一下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#include &lt;iostream&gt;</span>
<span class="n">using</span> <span class="n">namespace</span> <span class="n">std</span><span class="p">;</span>
<span class="n">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">auto</span> <span class="n">numbers</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span> <span class="p">};</span>
    <span class="n">auto</span> <span class="n">iterator_of_4</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">find</span><span class="p">(</span><span class="n">numbers</span><span class="o">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">numbers</span><span class="o">.</span><span class="n">end</span><span class="p">(),</span> <span class="mi">4</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">iterator_of_4</span> <span class="o">==</span> <span class="n">numbers</span><span class="o">.</span><span class="n">end</span><span class="p">())</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">未查找到</span> <span class="mi">4</span> <span class="err">的操作</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s2">&quot;Not found 4&quot;</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">代码执行</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s2">&quot;Got it&quot;</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>在使用迭代器的时候，我们往往要判断迭代器是否已经遍历到末尾，才可以去继续操作。因为有<strong>值不存在的情况</strong>，所以在以往的操作中都会使用<strong>一个特殊值来表示某种特殊的含义</strong>，通常情况下对于这种特殊值称作 <em>Sentinal Value</em>，在很多算法书中称其为<strong>哨兵值</strong>。使用哨兵值会有这么两个弊端：其一是<strong>形如 <code>std::find</code> 或者是 <code>std::binary_search</code> 这种方法都从它们各自的签名以及调用上，都无法得知它的错误情况，以及对应的错误情况处理方式</strong>。另外，以哨兵值的方式，使我们无法通过编译器来强制错误处理的行为。因为编译器对此是毫无感知的，其哨兵值都是由语言作者或是后期开发人员的约定俗成，例如 C 中文件读取的 <code>open</code> 函数，在读取失败下为 <code>-1</code>，或是上例中 <code>numbers.end()</code> 这个迭代位，只有在程序崩溃之后，才能显出原形。</p>
<p>为了突出 Optional 的必要性，<a href="https://boxueio.com/series/optional-is-not-an-option/ebook/138">泊学网</a>（笔者也是最近才看过的，这里推荐一下😎）中给出了一个哨兵值方案也无法解决的问题，这是一个 Objective-C 的例子：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">NSString</span> <span class="o">*</span><span class="n">tmp</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">[</span><span class="n">tmp</span> <span class="ss">rangeOfString</span><span class="p">:</span> <span class="err">@</span><span class="s2">&quot;Swift&quot;</span><span class="o">].</span><span class="n">location</span> <span class="o">!=</span> <span class="no">NSNotFound</span><span class="p">)</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="no">Will</span> <span class="nb">print</span> <span class="n">out</span> <span class="k">for</span> <span class="kp">nil</span> <span class="n">string</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;Something about swift&quot;</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p>虽然 <code>tmp</code> 的值为 <code>nil</code>，但调用 <code>tmp</code> 的 <code>rangeOfString</code> 方法却是合法的，它会返回一个值为 0 的 <code>NSRange</code> ，所以 <code>location</code> 的值也是 0。但是 <code>NSNotFound</code> 的值却是 <code>NSIntegerMax</code>。所以尽管 <code>tmp</code> 的值为 <code>nil</code>， 我们还能够在 Terminal 中看到 <code>Something about swift</code> 的输出。所以，当为 <code>nil</code> 的时候，我们仍旧需要特殊考虑。</p>
<p>于是，这就是 Optional 的由来，为了解决使用 Sentinal Value 约定而无法解决的问题。</p>
<h2 id="optional--1">使用 Optional 实现方法</h2>
<p>这里是 Swift Probe 系列，所以我们不说其用法。在 Swift 的源码中，Optional 以枚举类型来定义的：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@_fixed_layout</span>
<span class="kp">public</span> <span class="n">enum</span> <span class="no">Optional</span><span class="o">&lt;</span><span class="no">Wrapped</span><span class="o">&gt;</span> <span class="p">:</span> <span class="no">ExpressibleByNilLiteral</span> <span class="p">{</span>
	<span class="k">case</span> <span class="n">none</span>
	<span class="k">case</span> <span class="n">some</span><span class="p">(</span><span class="no">Wrapped</span><span class="p">)</span>
	<span class="kp">public</span> <span class="n">init</span><span class="p">(</span><span class="n">_</span> <span class="ss">some</span><span class="p">:</span> <span class="no">Wrapped</span><span class="p">)</span>
	<span class="kp">public</span> <span class="n">func</span> <span class="n">map</span><span class="p">(</span><span class="n">_</span> <span class="ss">transform</span><span class="p">:</span> <span class="p">(</span><span class="no">Wrapped</span><span class="p">)</span> <span class="n">throws</span> <span class="o">-&gt;</span> <span class="n">U</span><span class="p">)</span> <span class="n">rethrows</span> <span class="o">-&gt;</span> <span class="n">U?</span>
	<span class="kp">public</span> <span class="n">func</span> <span class="n">flatMap</span><span class="p">(</span><span class="n">_</span> <span class="ss">transform</span><span class="p">:</span> <span class="p">(</span><span class="no">Wrapped</span><span class="p">)</span> <span class="n">throws</span> <span class="o">-&gt;</span> <span class="n">U?</span><span class="p">)</span> <span class="n">rethrows</span> <span class="o">-&gt;</span> <span class="n">U?</span>
	<span class="kp">public</span> <span class="n">init</span><span class="p">(</span><span class="ss">nilLiteral</span><span class="p">:</span> <span class="p">())</span>
	<span class="kp">public</span> <span class="n">var</span> <span class="ss">unsafelyUnwrapped</span><span class="p">:</span> <span class="no">Wrapped</span> <span class="p">{</span> <span class="n">get</span> <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>当然在枚举中还有很多方法并没有列出，之后我们详细来谈。在枚举定义之前，有一个属性标识（attribute）  - <code>@_fixed_layout</code>，由此标识修饰的类型在 SIL （Swift intermediate
 Language）生成阶段进行处理。它的主要作用是将这个类型确定为固定布局，也就是在内存中这个类型的空间占用确定且无法改变。</p>
<p>由于 Optional 是多类型的，所以我们通过 <code>&lt;Wrapped&gt;</code> 来声明泛型。<code>ExpressibleByNilLiteral</code> 协议仅仅定义了一个方法：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">init</span><span class="p">(</span><span class="ss">nilLiteral</span><span class="p">:</span> <span class="p">())</span>    <span class="sr">//</span> <span class="err">使用</span> <span class="kp">nil</span> <span class="err">初始化一个实例</span></code></pre></div>
<p>不看方法，仅仅看这个枚举定义，其实我们就可以模拟一些很简单的方法。例如我们来解决上文中 C++ <code>std::find</code> 那个问题，对 <code>Array</code> 数据结构来写一个 <code>extension</code>：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">import</span> <span class="no">Foundation</span>
<span class="n">enum</span> <span class="no">Optional</span><span class="o">&lt;</span><span class="no">Wrapped</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">none</span>
    <span class="k">case</span> <span class="n">some</span><span class="p">(</span><span class="no">Wrapped</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">extension</span> <span class="nb">Array</span> <span class="n">where</span> <span class="ss">Element</span><span class="p">:</span> <span class="no">Equatable</span> <span class="p">{</span>
    <span class="n">func</span> <span class="n">find</span><span class="p">(</span><span class="n">_</span> <span class="ss">element</span><span class="p">:</span> <span class="no">Element</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="no">Optional</span><span class="o">&lt;</span><span class="no">Index</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="n">var</span> <span class="n">index</span> <span class="o">=</span> <span class="n">startIndex</span>
        <span class="k">while</span> <span class="n">index</span> <span class="o">!=</span> <span class="n">endIndex</span> <span class="p">{</span>
            <span class="k">if</span> <span class="nb">self</span><span class="o">[</span><span class="n">index</span><span class="o">]</span> <span class="o">==</span> <span class="n">element</span> <span class="p">{</span>
                <span class="k">return</span> <span class="o">.</span><span class="n">some</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="n">formIndex</span><span class="p">(</span><span class="ss">after</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">index</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="o">.</span><span class="n">none</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>代码很简单，就是将当前数组做一次遍历来查找这个元素，如果找到则返回一个  <code>some</code> 类别代表这个 Optional 结果是存在的。如果没有则返回 <code>none</code>。我们来测试一下：</p>
<p><img src="http://i2.kiimg.com/600799/e68a22fe9728f410.jpg" alt="" /></p>
<p>发现如果 <code>find</code> 方法在 <code>Array</code> 中无法找到对应元素，则会返回一个 <code>none</code> 的 Optional 对象。</p>
<p>由于在 Swift 的源码中已经定义了 Optional，并且使用特定的重载标记符号进行简化，所以我们也可以简写上述的 <code>find</code> ：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">extension</span> <span class="nb">Array</span> <span class="n">where</span> <span class="ss">Element</span><span class="p">:</span> <span class="no">Equatable</span> <span class="p">{</span>
    <span class="n">func</span> <span class="n">find</span><span class="p">(</span><span class="n">_</span> <span class="ss">element</span><span class="p">:</span> <span class="no">Element</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="no">Index</span><span class="p">?</span> <span class="p">{</span>
        <span class="n">var</span> <span class="n">index</span> <span class="o">=</span> <span class="n">startIndex</span>
        <span class="k">while</span> <span class="n">index</span> <span class="o">!=</span> <span class="n">endIndex</span> <span class="p">{</span>
            <span class="k">if</span> <span class="nb">self</span><span class="o">[</span><span class="n">index</span><span class="o">]</span> <span class="o">==</span> <span class="n">element</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">index</span>
            <span class="p">}</span>
            <span class="n">formIndex</span><span class="p">(</span><span class="ss">after</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">index</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="kp">nil</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>由于 Swift 通过 <code>?</code> 来对 Optional 类型做了简化，所以我们将返回值修改成 <code>Index?</code> 即可。其他地方也类似，如果有值直接返回，没有则返回 <code>nil</code>。我们使用 <code>if let</code> 范式来验证一下 Optional 的作用：</p>
<p><img src="http://i2.kiimg.com/600799/25548e1aecb79f8a.jpg" alt="" /></p>
<h2 id="optional--map--flatmap-">Optional 中 map 和 flatMap 实现</h2>
<p>在引入之前，我们来看以下代码：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">import</span> <span class="no">Foundation</span>
<span class="n">let</span> <span class="ss">author</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="o">=</span> <span class="s2">&quot;gua&quot;</span>
<span class="n">var</span> <span class="ss">AUTHOR</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="k">if</span> <span class="n">let</span> <span class="n">author</span> <span class="o">=</span> <span class="n">author</span> <span class="p">{</span>
    <span class="n">let</span> <span class="no">AUTHOR</span> <span class="o">=</span> <span class="n">author</span><span class="o">.</span><span class="n">uppercased</span><span class="p">()</span>
<span class="p">}</span></code></pre></div>
<p>我们通过一段小写的 Optional 字符串常量做出修改后来为其他进行赋值。那么如果我们 <code>AUTHOR</code> 是个常量应该怎么做呢？其实字符串就是一个包含字符量和 <code>nil</code> 量的集合，处理这种集合的时候使用 <code>map</code> 就可以解决了：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">var</span> <span class="ss">AUTHOR</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="o">=</span> <span class="n">author</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="vg">$0</span><span class="o">.</span><span class="n">uppercased</span><span class="p">()</span> <span class="p">}</span> <span class="sr">//</span> <span class="no">Optional</span><span class="p">(</span><span class="s2">&quot;GUA&quot;</span><span class="p">)</span></code></pre></div>
<p>这样我们就得到了一个新的 Optional 常量。那么 <code>map</code> 方法对于 Optional 量是怎么处理的呢？来阅读以下源码：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@_inlineable</span>
<span class="kp">public</span> <span class="n">func</span> <span class="n">map</span><span class="o">&lt;</span><span class="n">U</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">_</span> <span class="ss">transform</span><span class="p">:</span> <span class="p">(</span><span class="no">Wrapped</span><span class="p">)</span> <span class="n">throws</span> <span class="o">-&gt;</span> <span class="n">U</span>
    <span class="p">)</span> <span class="n">rethrows</span> <span class="o">-&gt;</span> <span class="n">U?</span> <span class="p">{</span>
    <span class="n">switch</span> <span class="nb">self</span> <span class="p">{</span>
    <span class="k">case</span> <span class="o">.</span><span class="n">some</span><span class="p">(</span><span class="n">let</span> <span class="n">y</span><span class="p">):</span>
        <span class="k">return</span> <span class="o">.</span><span class="n">some</span><span class="p">(</span><span class="n">try</span> <span class="n">transform</span><span class="p">(</span><span class="n">y</span><span class="p">))</span>
    <span class="k">case</span> <span class="o">.</span><span class="n">none</span><span class="p">:</span>
        <span class="k">return</span> <span class="o">.</span><span class="n">none</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>首先要说明的是 <code>Wrapped</code> ，这是 <code>Optional</code> 类型的泛型参数，表示 Optional 实际包装的的值类型。</p>
<p>另外来解释一下 <code>rethrows</code> 关键字：有这么一个场景，在很多方法中要传入一个闭包来执行，当传入的闭包中没有异常我们就不需要处理，有异常的时候，我们需要使用 <code>throws</code> 关键字来声明以下，代表我们需要进行异常处理。但是某些情况下，一个闭包函数本身不会产生异常，但是作为其他函数的参数就会出现异常情况。这时候我们使用 <code>rethrows</code> 对函数进行声明从而向上层传递异常情况。</p>
<p>暂且我们先不去考虑异常情况，根据源码的思路自行实现一个 <code>map</code> 方法来处理 Optional 问题：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">extension</span> <span class="no">Optional</span> <span class="p">{</span>
    <span class="n">func</span> <span class="n">myMap</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="ss">transform</span><span class="p">:</span> <span class="p">(</span><span class="no">Wrapped</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T?</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">let</span> <span class="n">value</span> <span class="o">=</span> <span class="nb">self</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">transform</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="kp">nil</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>很简单的就实现了等同之前 <code>map</code> 效果的功能。</p>
<p>根据此处的 <code>map</code> 实现，继续引入下一个示例：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span> <span class="ss">stringOne</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="o">=</span> <span class="s2">&quot;1&quot;</span>
<span class="n">let</span> <span class="n">ooo</span> <span class="o">=</span> <span class="n">stringOne</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="no">Int</span><span class="p">(</span><span class="vg">$0</span><span class="p">)</span> <span class="p">}</span> <span class="sr">//</span> <span class="no">Optional</span><span class="o">&lt;</span><span class="no">Optional</span><span class="o">&lt;</span><span class="no">Int</span><span class="o">&gt;&gt;</span></code></pre></div>
<p>由于 <code>Int($0)</code> 会返回一个 <code>Int?</code> 的 Optional 量，而 <code>map</code> 由之前的源码可知，又会返回一个 Optional 类型，因此 <code>ooo</code> 变量就是一个双层嵌套 Optional 对象。而我们希望的仅仅是返回一个 <code>Int</code> 型整数就好了，此时引入 <code>flatMap</code> 来解决这个问题：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span> <span class="ss">stringOne</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="o">=</span> <span class="s2">&quot;1&quot;</span>
<span class="n">let</span> <span class="n">ooo</span> <span class="o">=</span> <span class="n">stringOne</span><span class="o">.</span><span class="n">flatMap</span> <span class="p">{</span> <span class="no">Int</span><span class="p">(</span><span class="vg">$0</span><span class="p">)</span> <span class="p">}</span> <span class="sr">//</span> <span class="no">Optional</span><span class="o">&lt;</span><span class="no">Int</span><span class="o">&gt;</span></code></pre></div>
<p><code>flatMap</code> 与 <code>map</code> 的区别是对 closure 参数的返回值进行处理，之后对其值直接返回，而不会像 <code>map</code> 一样对其进行一次 <code>.some()</code> 的 Optional 封装：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@_inlineable</span>
<span class="kp">public</span> <span class="n">func</span> <span class="n">flatMap</span><span class="o">&lt;</span><span class="n">U</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">_</span> <span class="ss">transform</span><span class="p">:</span> <span class="p">(</span><span class="no">Wrapped</span><span class="p">)</span> <span class="n">throws</span> <span class="o">-&gt;</span> <span class="n">U?</span>
    <span class="p">)</span> <span class="n">rethrows</span> <span class="o">-&gt;</span> <span class="n">U?</span> <span class="p">{</span>
    <span class="n">switch</span> <span class="nb">self</span> <span class="p">{</span>
    <span class="k">case</span> <span class="o">.</span><span class="n">some</span><span class="p">(</span><span class="n">let</span> <span class="n">y</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">try</span> <span class="n">transform</span><span class="p">(</span><span class="n">y</span><span class="p">)</span>
    <span class="k">case</span> <span class="o">.</span><span class="n">none</span><span class="p">:</span>
        <span class="k">return</span> <span class="o">.</span><span class="n">none</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>以上就是对于 Optional 的 <code>map</code> 和 <code>flatMap</code> 分析。</p>
<h2 id="nil-coalescing-">Nil Coalescing 实现</h2>
<p>有时候我们需要在 Optional 值为 <code>nil</code> 的时候，设定一个默认值。用以往的方法，肯定会使用三元操作符：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">var</span> <span class="ss">userInput</span><span class="p">:</span> <span class="nb">String</span><span class="p">?</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="n">let</span> <span class="n">username</span> <span class="o">=</span> <span class="n">userInput</span> <span class="o">!=</span> <span class="kp">nil</span> <span class="p">?</span> <span class="n">userInput!</span> <span class="p">:</span> <span class="s2">&quot;Gua&quot;</span></code></pre></div>
<p>如此写法过于冗长，对开发者十分不友好。为了表意清晰，代码方便，Swift 引入了 Nil Coalescing 来简化书写。于是之前的 <code>username</code> 的定义可以简写成这样：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span> <span class="n">username</span> <span class="o">=</span> <span class="n">userInput</span> <span class="sc">??</span> <span class="s2">&quot;Gua&quot;</span></code></pre></div>
<p><code>??</code> 操作符强制要求可能为 <code>nil</code> 的变量要写在左边，默认值写在右边，这样也统一了代码风格。我们深入到源码来看 Nil Coalescing 操作符的实现：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@_transparent</span>
<span class="kp">public</span> <span class="n">func</span> <span class="sc">??</span> <span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="ss">optional</span><span class="p">:</span> <span class="n">T?</span><span class="p">,</span> <span class="ss">defaultValue</span><span class="p">:</span> <span class="vi">@autoclosure</span> <span class="p">()</span> <span class="n">throws</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">)</span> <span class="n">rethrows</span> <span class="o">-&gt;</span> <span class="n">T</span> <span class="p">{</span>
    <span class="n">switch</span> <span class="n">optional</span> <span class="p">{</span>
    <span class="k">case</span> <span class="o">.</span><span class="n">some</span><span class="p">(</span><span class="n">let</span> <span class="n">value</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">value</span>
    <span class="k">case</span> <span class="o">.</span><span class="n">none</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">try</span> <span class="n">defaultValue</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>解释两个标记：</p>
<ol>
  <li><code>@_transparent</code>：标明该函数应该在 pipeline 中更早的进行函数内联操作。用于非常原始、简单的函数操作。他与 <code>@_inline</code> 的区别就是在没有优化设置的 debug 模式下也会使得函数内连接，与 <code>@_inline (__always)</code> 标记十分相似。</li>
  <li><code>@autoclosure</code>：这个标记在 @Onevcat 的 <a href="http://swifter.tips/autoclosure/">Swifter Tips</a> 用已经有很好的介绍和实用场景说明。其作用是<strong>将一句表达式自动地封装成一个闭包</strong>。这样封装的目的是当默认值是经过一系列计算得到结构环境下，实用 <code>@autoclosure</code> 封装会简化传统闭包的开销，因为如果是传统闭包需要先执行再判断，而 <code>@autoclosure</code> 巧妙的避免了这一点。</li>
</ol>
<h2 id="section">结语</h2>
<p>Swift 源码分析是笔者一直想开的新坑。本文仅仅介绍了 Optional 的实现中最核心的部分，然而只是 Swift 的冰山一角。希望与读者多多交流，共同进步。</p>
<h2 id="section-1">参考文献</h2>
<p><a href="https://github.com/apple/swift/blob/master/stdlib/public/core/Optional.swift">Apple Swift Source Code</a></p>
<p><a href="http://blog.devtang.com/2016/02/27/swift-gym-1-nested-optional/">Swift 烧脑体操（一） - Optional 的嵌套</a></p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《Swift Probe - Optional》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/swift-optional/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/swift-optional/&amp;title=《Swift Probe - Optional》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/swift-optional/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/swift-optional/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>